/*
 * Created on 2006-3-15
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package auditoryUIbk;
import java.util.*;
import java.io.*;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;

/**
 * Various useful things that didn't fit elsewhere
 */
public class Misc
{
  private Misc() {}
  
  /**
   * I couldn't find a routine to correctly convert a double to a string
   * with a specified number of significant figures, so I wrote one myself.
   * I believe it works in all cases, but would be very appreciative
   * of bug reports if it does not.
   */
  public static String toString(double x, int sigFigs)
  {
    StringBuffer s;

    if (sigFigs < 1) {
      throw new IllegalArgumentException("sigFigs must be at least 1");
    }
    
    if (x == 0) {
      s = new StringBuffer("0");
      if (sigFigs > 1) {
	s.append(".");
	for (int i = 1; i < sigFigs; i++) {
	  s.append("0");
	}
      }
    }
    else {
      boolean negative = false;

      if (x < 0) {
	negative = true;
	x = -x;
      }

      final double log10 = Math.log(10);
      int exp = (int) Math.floor(Math.log(x) / log10);
      s = new StringBuffer(sigFigs);	// might be bigger, but this
      // is a good first guess

      // scale up and round to appropriate place
      final long intVal = Math.round(x * Math.pow(10, sigFigs - exp - 1));
      String digits = Long.toString(intVal);

      // handle the case where rounding adds another digit (e.g.,
      // 99.99 could round to 100.0)
      if (digits.length() > sigFigs) {
	StringBuffer tmp = new StringBuffer(digits);
	tmp.setLength(sigFigs);
	digits = tmp.toString();
	exp++;
      }

      int dIndex = 0;
      if (exp >= 0) {
	if (exp < sigFigs) {
	  s.append(digits.substring(0, exp+1));
	  dIndex += exp+1;
	  if (dIndex < sigFigs) {
	    s.append(".");
	  }
	  s.append(digits.substring(dIndex));
	}
	else {
	  s.append(digits);
	  for (int i = digits.length(); i <= exp; i++)
	    s.append('0');
	}
      }
      else {
	s.append("0.");
	for (int i = -1; i > exp; i--) {
	  s.append('0');
	}
	s.append(digits.substring(dIndex));
      }

      if (negative)
	s.insert(0, '-');
    }
    
    return s.toString();
  }

  public static Double[] promoteArray(double[] a)
  {
    Double[] result = new Double[a.length];

    for (int i = 0; i < a.length; i++)
      result[i] = new Double(a[i]);
    return result;
  }

  public static Double[][] promoteMatrix(double[][] m)
  {
    Double[][] result = new Double[m.length][];
    
    for (int i = 0; i < m.length; i++)
      result[i] = promoteArray(m[i]);
    return result;
  }

  /** Return the index of the highest value (or -1 if a has no elements). */
  public static int maxIndex(Number[] a)
  {
    if (a.length == 0) {
      return -1;
    }
    int result = 0;
    for (int i = 1; i < a.length; i++) {
      if (a[i].doubleValue() > a[result].doubleValue()) {
	result = i;
      }
    }
    return result;
  }

  /** Return the index of the highest absolute value (or -1 if a has
      no elements). */
  public static int maxAbsIndex(Number[] a)
  {
    if (a.length == 0) {
      return -1;
    }
    int result = 0;
    for (int i = 1; i < a.length; i++) {
      if (a[i].doubleValue() > Math.abs(a[result].doubleValue())) {
	result = i;
      }
    }
    return result;
  }
  public static int sgn(double d)
  {
    return (d == 0.0) ? 0 : ((d < 0) ? -1 : 1);
  }
  
  public static BitSet not(BitSet bs)
  {
    BitSet result = new BitSet(bs.size());
    
    for (int i = 0; i < bs.size(); i++) {
      if (!bs.get(i))
	result.set(i);
    }
    return result;
  }

/*  public static void shuffle(int[] a)
  {
    shuffle(a, new Random());
  }
*/
  public static void shuffle(int[] a, Random r)
  {
    for (int i = a.length-1; i > 0; i--) {
      int j = (int) Math.floor(r.nextDouble() * i);
      if (j != i) {
	int temp;
	temp = a[i];
	a[i] = a[j];
	a[j] = temp;
      }
    }
  }
  
/*  public static void shuffle(Object[] a)
  {
    shuffle(a, new Random());
  }
*/
  public static void shuffle(Object[] a, Random r)
  {
    for (int i = a.length-1; i > 0; i--) {
      int j = (int) Math.floor(r.nextDouble() * i);
      if (j != i) {
	Object temp;
	temp = a[i];
	a[i] = a[j];
	a[j] = temp;
      }
    }
  }

/*  public static void shuffle(Vector v)
  {
    shuffle(v, new Random());
  }
  */
  public static void shuffle(Vector v, Random r)
  {
    int numElements = v.size();
    for (int i = numElements-1; i > 0; i--) {
      int j = (int) Math.floor(r.nextDouble() * i);
      if (j != i) {
	Object temp;
	temp = v.elementAt(i);
	v.setElementAt(v.elementAt(j), i);
	v.setElementAt(temp, j);
      }
    }
  }
  
  public static void bitStringTest(String args[])
  {
    if (args.length < 1) {
      System.err.println("Misc: usage: java Misc bitstring");
      System.exit(-1);
    }
    BitSet bs = new BitSet(args[0].length());
    for (int i = 0; i < args[0].length(); i++)
      if (args[0].charAt(i) == '1')
	bs.set(i);
    System.out.println(bs.toString() + "\t" + not(bs).toString());
  }
  
  public static void main(String args[])
  {
    if (args.length < 2) {
      System.err.println("Misc: usage: java Misc x sigFigs");
      System.exit(-1);
    }
    double x = Double.valueOf(args[0]).doubleValue();
    int sf = Integer.valueOf(args[1]).intValue();
    System.out.println(toString(x, sf));
  }

  public static String ordinalSuffix(int n)
  {
    int ones = n % 10;
    String result;
    switch (ones) {
    case 1:
      result = "st";
      break;
    case 2:
      result = "nd";
      break;
    case 3:
      result = "rd";
      break;
    default:
      result = "th";
      break;
    }
    return result;
  }

  public static String ordinal(int n)
  {
    return n + ordinalSuffix(n);
  }

  public static void printVector(PrintStream out, double[] v, int sigFigs)
  {
    out.print("{ ");
    for (int i = 0; i < v.length; i++) {
      out.print(toString(v[i], sigFigs));
      if (i < (v.length-1)) {
	out.print(", ");
      }
    }
    out.print(" }");
  }

  public static int indexOf(Object[] array, Object target)
  {
    for (int i = 0; i < array.length; i++) {
      if (array[i] == target) {
	return i;
      }
    }
    return -1;
  }

  public static Object findLargest(Collection collection, Comparator comp)
  {
    return findLargest(collection.iterator(), comp);
  }
  
  public static Object findLargest(Iterator iter, Comparator comp)
  {
    if (iter.hasNext()) {
      Object largest = iter.next();
      while (iter.hasNext()) {
	Object obj = iter.next();
	if (comp.compare(obj, largest) == 1) {
	  largest = obj;
	}
      }
      return largest;
    }
    return null;
  }

  /** Return an empty collection of the same type as c, if possible
      (using Class.newInstance()).  If that fails, make an
      ArrayList. */
  public static Collection makeEmptyClone(Collection c)
  {
    Collection result = null;
    try {
      result = (Collection) c.getClass().newInstance();
    }
    catch (InstantiationException e) { }
    catch (IllegalAccessException e) { }
    if (result == null) {
      result = new ArrayList(c.size());
    }
    return result;
  }
  
  public interface UnaryOperator {
    Object operate(Object obj);
  }

  public final static UnaryOperator IDENTITY_OPERATOR =
    new UnaryOperator() {
      public Object operate(Object obj)
      {
	return obj;
      }
    };
  
  /** Apply op to all elements of in, and make a new Collection of the
      results.  It tries to make a new Collection of the same type as
      in.  If this fails it returns an ArrayList. */
  public static Collection mapcar(Collection in, UnaryOperator op)
  {
    Collection result = makeEmptyClone(in);
    for (Iterator iter = in.iterator(); iter.hasNext();) {
      result.add(op.operate(iter.next()));
    }
    return result;
  }

  public static List mapcar(Iterator iter, UnaryOperator op)
  {
    List result = new ArrayList();
    for (; iter.hasNext();) {
      result.add(op.operate(iter.next()));
    }
    return result;
  }
  
  /** Return a Collection that contains all elements of in for which
      acceptor returns true. */
  public static Collection accept(Collection in, Acceptor acceptor)
  {
    Collection result = makeEmptyClone(in);
    for (Iterator iter = in.iterator(); iter.hasNext();) {
      Object obj = iter.next();
      if (acceptor.accept(obj)) {
	result.add(obj);
      }
    }
    return result;
  }

  public interface Acceptor {
    boolean accept(Object obj);
  }

  /** Clones the collection.  Does not clone its members. */
  public static Collection cloneCollection(Collection c)
  {
    return cloneCollection(c, false);
  }

  /** Invoke the clone() method and return the result, or null if it
      fails. */
  public static Object invokeClone(Object o)
  {
    Object result = null;
    try {
      Method cloneMethod = o.getClass().getDeclaredMethod("clone", null);
      try {
	result = cloneMethod.invoke(o, null);
      }
      catch (IllegalAccessException e) {}
      catch (IllegalArgumentException e) {}
      catch (InvocationTargetException e) {}
    }
    catch (NoSuchMethodException e) {}
    catch (SecurityException e) {}
    return result;
  }
  
  /** If cloneMembers, clone the members of the collection if
      possible.  If one or more members cannot be cloned, they will
      silently not be. */
  public static Collection cloneCollection(Collection c,
					   boolean cloneMembers)
  {
    Collection result;
    if (cloneMembers) {
      result = mapcar(c, new UnaryOperator() {
	public Object operate(Object obj) {
	  Object newObj;
	  if (obj == null) {
	    newObj = obj;
	  }
	  else {
	    newObj = invokeClone(obj);
	  }
	  return newObj;
	}
      });
    }
    else {
      result = null;
      try {
	result = (Collection) c.getClass().newInstance();
      }
      catch (InstantiationException e) { }
      catch (IllegalAccessException e) { }
      if (result == null) {
	if (result instanceof Set) {
	  result = new HashSet(c.size());
	}
	else {
	  result = new ArrayList(c.size());
	}
      }
      result.addAll(c);
    }
    return result;
  }

  /** make a deep copy of a, as far as possible.  If the
      Collection contains Collections or arrays, they are deep copied,
      too. */
  public static Object[] deepCopy(Object[] a)
  {
    Object[] result = (Object[]) a.clone();
    for (int i = 0; i < a.length; i++) {
      if (a[i] instanceof Collection) {
	result[i] = deepCopy((Collection) a[i]);
      }
      else if (a[i] instanceof Object[]) {
	result[i] = deepCopy((Object[]) a[i]);
      }
      else {
	Object temp = invokeClone(a[i]);
	if (temp != null) {
	  result[i] = temp;
	}
      }
    }
    return result;
  }
  
  /** make a deep copy of c, as far as possible.  If the
      Collection contains Collections or arrays, they are deep copied,
      too. */
  public static Collection deepCopy(Collection c)
  {
    Collection result = null;
    try {
      result = (Collection) c.getClass().newInstance();
    }
    catch (InstantiationException e) { }
    catch (IllegalAccessException e) { }
    if (result == null) {
      result = new ArrayList(c.size());
    }
    for (Iterator iter = c.iterator(); iter.hasNext();) {
      Object obj = iter.next();
      Object newObj;
      if (obj instanceof Collection) {
	newObj = deepCopy((Collection) obj);
      }
      else if (obj instanceof Object[]) {
	newObj = deepCopy((Object[]) obj);
      }
      else {
	Object temp = invokeClone(obj);
	if (temp != null) {
	  newObj = temp;
	}
	else {
	  newObj = obj;
	}
      }
      result.add(newObj);
    }
    return result;
  }

  public static Object deepCopy(Object obj)
  {
    if (obj instanceof Collection) {
      return deepCopy((Collection) obj);
    }
    else if (obj instanceof Object[]) {
      return deepCopy((Object[]) obj);
    }
    else if (obj instanceof Map) {
      return deepCopy((Map) obj);
    }
    else {
      Object newObj = invokeClone(obj);
      return (newObj == null) ? obj : newObj;
    }
  }

  public interface MapAcceptor {
    boolean accept(Object key, Object value);
  }
  
  public static Map deepCopy(Map m)
  {
    Map result = null;
    try {
      result = (Map) m.getClass().newInstance();
    }
    catch (InstantiationException e) { }
    catch (IllegalAccessException e) { }
    if (result == null) {
      result = new HashMap(m.size());
    }
    Set keys = m.keySet();
    for (Iterator iter = keys.iterator(); iter.hasNext();) {
      Object key = iter.next();
      Object value = m.get(key);
      result.put(deepCopy(key), deepCopy(value));
    }
    return result;
  }

  /** deep copy only key/value pairs that are accepted by acceptor */
  public static Map deepCopy(Map m, MapAcceptor acceptor)
  {
    Map result = null;
    try {
      result = (Map) m.getClass().newInstance();
    }
    catch (InstantiationException e) { }
    catch (IllegalAccessException e) { }
    if (result == null) {
      result = new HashMap(m.size());
    }
    Set keys = m.keySet();
    for (Iterator iter = keys.iterator(); iter.hasNext();) {
      Object key = iter.next();
      Object value = m.get(key);
      if (acceptor.accept(key, value)) {
	result.put(deepCopy(key), deepCopy(value));
      }
    }
    return result;
  }

  public static String escapeString(String s)
  {
    StringBuffer result = new StringBuffer();
    char[] c = s.toCharArray();
    result.append('\'');
    for (int i = 0; i < c.length; i++) {
      switch (c[i]) {
      case '\\':
      case '\'':
	result.append('\\');
      default:
	result.append(c[i]);
	break;
      }
    }
    result.append('\'');
    return result.toString();
  }

  public static String readEscapedString(Reader reader)
    throws ParseException, IOException
  {
    return readEscapedString(reader, false);
  }
  
  public static String readEscapedString(Reader reader,
					 boolean ignoreInitialWhitespace)
    throws ParseException, IOException
  {
    StringBuffer result = new StringBuffer();
    char c;
    boolean done = false;
    boolean validBeginning = false;
    if (ignoreInitialWhitespace) {
      while (!done) {
	c = (char) reader.read();
	if (Character.isSpaceChar(c)) {
	  // ignore it
	}
	else {
	  if (c == '\'') {
	    validBeginning = true;
	  }
	  done = true;
	}
      }
      done = false;
    }
    else {
      validBeginning = (reader.read() == '\'');
    }
    if (!validBeginning) {
      throw new
	ParseException("First character was not a single quote (')", 0);
    }
    boolean escapeNextChar = false;
    while (!done) {
      c = (char) reader.read();
      if (escapeNextChar) {
	result.append(c);
	escapeNextChar = false;
      }
      else {
	switch (c) {
	case '\\':
	  escapeNextChar = true;
	  break;
	case '\'':
	  done = true;
	  break;
	default:
	  result.append(c);
	}
      }
    }
    return result.toString();
  }

  /** returns true if both a and b are null, or if both have the same
      string value (as determined by equals()) */
  public static boolean areEqualOrNull(String a, String b)
  {
    return ((a != null) && a.equals(b)) || (a == null && b == null);
  }

  /** return -1 if x is negative, 0 if x is 0, or 1 if x is positive */
  public static int sign(double x)
  {
    if (x < 0) {
      return -1;
    }
    else if (x > 0) {
      return 1;
    }
    else
      return 0;
  }
}
